//
// Created by mouse on 2019/12/19.
//

#include "app_ir.h"

static IRsend irsend(K_SEND_PIN);
static IRrecv irrecv(K_RECV_PIN, K_CAPTURE_BUFFER_SIZE, K_TIMEOUT, false);

static String setInfraredDataTopic;
static String postInfraredDataTopic;

static decode_results decodeResults;

static Ticker irSendTimer;

static void jsonstr_to_unknown_infrared_data(IrResults *results, JsonObject obj)
{
    results->size = obj["size"];
    results->rawData = new uint16_t[results->size];
    for (int i = 0; i < results->size; i++)
    {
        results->rawData[i] = obj["rawData"][i];
    }
}

static void jsonstr_to_command_infrared_data(IrResults *results, JsonObject obj)
{
    results->bits = TypeConversionUtils::string_to_uint64(obj["bits"], 10);
    results->value = TypeConversionUtils::string_to_uint64(obj["value"], 16);
    results->address = TypeConversionUtils::string_to_uint64(obj["address"], 16);
    results->command = TypeConversionUtils::string_to_uint64(obj["command"], 16);
}

static void jsonstr_to_state_infrared_data(IrResults *results, JsonObject obj)
{
    results->bits = TypeConversionUtils::string_to_uint64(obj["bits"], 10);
    uint16_t nbytes = results->bits / 8;
    results->state[nbytes] = 0;

    String state = obj["state"];
    for (uint16_t i = 0; i < nbytes; i++)
    {
        results->state[i] = TypeConversionUtils::string_to_uint64(
                String(state[i * 2]) + String(state[i * 2 + 1]),
                16
        );
    }
}

static IrResults *jsonstr_to_infrared_data(String jsonStr)
{
    auto *results = new IrResults();

    const size_t capacity = 2048;
    DynamicJsonDocument doc(capacity);

    deserializeJson(doc, jsonStr);

    JsonObject obj = doc.as<JsonObject>();

    bool hasACState = obj["hasACState"];
    results->decode_type = strToDecodeType(obj["protocol"]);

    if (results->decode_type == decode_type_t::UNKNOWN)
    {
        jsonstr_to_unknown_infrared_data(results, obj);
    } else if (hasACState)
    {
        jsonstr_to_state_infrared_data(results, obj);
    } else
    {
        jsonstr_to_command_infrared_data(results, obj);
    }

    return results;
}

static String unknown_infrared_data_to_jsonstr(decode_results results)
{
    String output;

    uint16_t *raw_array = resultToRawArray(&results);
    uint16_t size = getCorrectedRawLength(&results);

    const size_t capacity = JSON_ARRAY_SIZE(size) + JSON_OBJECT_SIZE(4) + 50;
    DynamicJsonDocument doc(capacity);

    doc["hasACState"] = false;
    doc["protocol"] = typeToString(results.decode_type);
    doc["size"] = size;
    JsonArray data = doc.createNestedArray("rawData");
    for (uint16_t i = 0; i < size; i++)
    {
        data.add(raw_array[i]);
    }

    serializeJson(doc, output);

    free(raw_array);

    return output;
}

static String command_infrared_data_to_jsonstr(decode_results results)
{
    String output;

    const size_t capacity = JSON_OBJECT_SIZE(6) + 50;
    DynamicJsonDocument doc(capacity);

    doc["hasACState"] = false;
    doc["protocol"] = typeToString(results.decode_type);
    doc["bits"] = results.bits;
    if (results.address > 0 || results.command > 0)
    {
        doc["address"] = TypeConversionUtils::uint64_to_string(results.address, 16);
        doc["command"] = TypeConversionUtils::uint64_to_string(results.command, 16);
    }
    doc["value"] = TypeConversionUtils::uint64_to_string(results.value, 16);

    serializeJson(doc, output);

    return output;
}

static String state_infrared_data_to_jsonstr(decode_results results)
{
    String output;

    const size_t capacity = JSON_OBJECT_SIZE(5) + 50;
    DynamicJsonDocument doc(capacity);

    doc["hasACState"] = true;
    doc["protocol"] = typeToString(results.decode_type);
    doc["bits"] = results.bits;
    String state = "";
    uint16_t nbytes = results.bits / 8;
    for (uint16_t i = 0; i < nbytes; i++)
    {
        if (results.state[i] < 0x10)
            state += '0';
        state += TypeConversionUtils::uint64_to_string(results.state[i], 16);
    }
    doc["state"] = state;

    serializeJson(doc, output);

    return output;
}

static void print_lr_recv(decode_results results)
{
    // Display a crude timestamp.
    uint32_t now = millis();
    LOG_DEBUG(D_STR_TIMESTAMP
                      " : %06u.%03u\r\n", now / 1000, now % 1000);

    // Check if we got an IR message that was to big for our capture buffer.
    if (results.overflow)
        LOG_DEBUG(D_WARN_BUFFERFULL
                          "\r\n", K_CAPTURE_BUFFER_SIZE);

    // Display the library version the message was captured with.
    Serial.println(D_STR_LIBRARY "   : v" _IRREMOTEESP8266_VERSION_ "\r\n");

    // Display the basic output of what we found.
    Serial.print(resultToHumanReadableBasic(&results));

    // Display any extra A/C info if we have it.
    String description = IRAcUtils::resultAcToString(&results);
    if (description.length()) Serial.println(D_STR_MESGDESC ": " + description);
    yield();  // Feed the WDT as the text output can take a while to print.

#if LEGACY_TIMING_INFO
    // Output legacy RAW timing info of the result.
    Serial.println(resultToTimingInfo(&results));
    yield();  // Feed the WDT (again)
#endif  // LEGACY_TIMING_INFO

    // Output the results as source code
    Serial.println(resultToSourceCode(&results));
    Serial.println();    // Blank line between entries
}

void irSendTimerHandler(IrResults *ir)
{
    decode_type_t protocol = ir->decode_type;

    irrecv.disableIRIn();
    if (protocol == decode_type_t::UNKNOWN)
    {
        irsend.sendRaw(ir->rawData, ir->size, K_FREQUENCY);
    } else if (hasACState(protocol))
    {
        irsend.send(ir->decode_type, ir->state, ir->bits / 8);
    } else
    {
        irsend.send(ir->decode_type, ir->value, ir->bits);
    }
    irrecv.enableIRIn();

    delete ir->rawData;
    delete ir;
}

void InfraredTask::setup()
{
    irsend.begin();
    irrecv.setUnknownThreshold(K_MIN_UNKNOWN_SIZE);
    irrecv.enableIRIn();

    if (!setInfraredDataTopic.isEmpty())
    {
        _networkTask->addSubscribeTopic(setInfraredDataTopic, [](String &topic, String &payload) {
            IrResults *results = jsonstr_to_infrared_data(payload);
            irSendTimer.once(0, irSendTimerHandler, results);
        }, 0);
    }

}

InfraredTask::InfraredTask(NetworkTask *networkTask)
{
    _networkTask = networkTask;

    if (!String(configInfo->device_key).isEmpty())
    {
        auto *topic = new MqttUtils::TopicEntity();

        strcpy(topic->title, MQTT_TOPIC_USER);
        strcpy(topic->product, "*");
        strcpy(topic->device, configInfo->device_key);
        strcpy(topic->function, "set");
        strcpy(topic->item, "infraredData");
        setInfraredDataTopic = MqttUtils::toTopicString(topic);

        strcpy(topic->title, MQTT_TOPIC_USER);
        strcpy(topic->product, "*");
        strcpy(topic->device, configInfo->device_key);
        strcpy(topic->function, "post");
        strcpy(topic->item, "infraredData");
        postInfraredDataTopic = MqttUtils::toTopicString(topic);

        delete topic;
    }

}

void InfraredTask::loop()
{
    if (irrecv.decode(&decodeResults))
    {
//        print_lr_recv(decodeResults);
//            Serial.println(uint64ToString(results.value, 16));

        String infraredData = "";

        if (decodeResults.decode_type == decode_type_t::UNKNOWN)
        {
            infraredData = unknown_infrared_data_to_jsonstr(decodeResults);
        } else if (hasACState(decodeResults.decode_type))
        {
            infraredData = state_infrared_data_to_jsonstr(decodeResults);
        } else
        {
            infraredData = command_infrared_data_to_jsonstr(decodeResults);
        }

        _networkTask->messageSend(postInfraredDataTopic, infraredData, false, 0);

        irrecv.resume();

    }
}